/*
 * Benchmark copy functions
 * - Allocate a 4KB source buffer, filled with a pattern
 * - Get the onchip memory resource and fill the first 4KB with a pattern
 * - Allocate a 3MB destination buffer
 *
 * For several copy fuctions, copy the source/onchip buffer to fill the destination buffer.
 * This is done nloops times.
 *
 * Check that the destination buffer is filled with the right pattern.
 *
 * gcc -O2 -Wall -D__USE_INLINE__ -o copymem_4kb copymem_4kb.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <sys/time.h>

#ifdef __amigaos4__
#include <proto/exec.h>
#include <proto/onchipmem.h>
#include <resources/onchipmem.h>

struct Library *ocm_res = NULL;
struct OCMIFace *IOCM = NULL;
#else
#define TRUE 1
#define FALSE 0
#endif

#define NB_SECONDS 12

#define SRC_BUFFER_SIZE_BYTES 4096
#define SRC_BUFFER_SIZE_WORDS (SRC_BUFFER_SIZE_BYTES / sizeof(int))

#define DST_BUFFER_SIZE_BYTES (1024 * 768 * 4)
#define DST_BUFFER_SIZE_WORDS (DST_BUFFER_SIZE_BYTES / sizeof(int))
#define DST_BUFFER_SIZE_4KB (DST_BUFFER_SIZE_BYTES / 4096)
#define DST_BUFFER_SIZE_1MB (DST_BUFFER_SIZE_BYTES / (1024 * 1024))

static void buffer_fill(int *buffer, int len, int value)
{
   int i;

   for (i=0 ; i<len ; i++) {
      buffer[i] = value;
   }
}

// Check that a buffer is filled with a given value.
// Return TRUE if success
static int buffer_check_fill_value(int *buffer, int len, int value)
{
   int i;

   for (i=0 ; i<len ; i++) {
      if (buffer[i] != value) {
         printf("buffer_check_fill_value failed at offset %d (expected 0x%08x, found 0x%08x)\n", i, value, buffer[i]);
         return FALSE;
      }
   }

   return TRUE;
}

int main(int argc, char **argv)
{
   int *buf;
   int *src_buf = NULL;
   int *dst_buf = NULL;
   int i, j, n, nloops;
   struct timeval tv_start, tv_stop, tv_diff;
   float ftime;
#ifndef __amigaos4__
   int *src_buf_addr = NULL;
   int *dst_buf_addr = NULL;
#endif

#ifdef __amigaos4__
   int *ocm_buf = NULL;
   nloops = 1000;
#else
   nloops = 5000;
#endif

#ifdef __amigaos4__
   ocm_res = OpenResource("onchipmem.resource");
   if (ocm_res) {
      IOCM = (struct OCMIFace *)GetInterface((struct Library *)ocm_res, "main", 1, NULL);
      if (IOCM) {
          ocm_buf = (int *)ObtainOnChipMem();
          printf("Obtained OnChip memory at %p\n", ocm_buf);
          buffer_fill(ocm_buf, SRC_BUFFER_SIZE_WORDS, 0xbbbbbbbb);
      }
   }

   src_buf = (int *)AllocVecTags(SRC_BUFFER_SIZE_BYTES, AVT_Type, MEMF_PRIVATE, AVT_PhysicalAlignment, 4096, TAG_END);
   if (src_buf) {
      printf("Allocated standard memory at %p\n", src_buf);
   } else {
      printf("Impossible to allocate source buffer\n");
      return 1;
   }
   buffer_fill(src_buf, SRC_BUFFER_SIZE_WORDS, 0xbbbbbbbb);

   dst_buf = (int *)AllocVecTags(DST_BUFFER_SIZE_BYTES, AVT_Type, MEMF_PRIVATE, AVT_PhysicalAlignment, 4096, TAG_END);
   if (!dst_buf) {
      printf("Impossible to allocate destination buffer\n");
      return 1;
   }
#else
   src_buf_addr = (int *)malloc(SRC_BUFFER_SIZE_BYTES * 2);
   if (src_buf_addr) {
      printf("Allocated standard memory at %p\n", src_buf_addr);
      if ((int)src_buf_addr & 0xffff) {
         src_buf = (int *)(((int)src_buf_addr + SRC_BUFFER_SIZE_BYTES) & ~(SRC_BUFFER_SIZE_BYTES - 1));
         printf("Buffer not aligned, realign it at %p\n", src_buf);
      }
   } else {
      printf("Impossible to allocate source buffer\n");
      return 1;
   }
   buffer_fill(src_buf, SRC_BUFFER_SIZE_WORDS, 0xbbbbbbbb);

   dst_buf_addr = (int *)malloc(DST_BUFFER_SIZE_BYTES + SRC_BUFFER_SIZE_BYTES);
   if (dst_buf_addr) {
      printf("Allocated standard memory at %p\n", dst_buf_addr);
      dst_buf = (int *)(((int)dst_buf_addr + SRC_BUFFER_SIZE_BYTES) & ~(SRC_BUFFER_SIZE_BYTES - 1));
      printf("Buffer aligned at %p\n", dst_buf);
   } else {
      printf("Impossible to allocate destination buffer\n");
      return 1;
   }
   buffer_fill(dst_buf, DST_BUFFER_SIZE_WORDS, 0xbbbbbbbb);
#endif

   /*
    * int
    */
   buffer_fill(dst_buf, DST_BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      buf = dst_buf;
      for (i=0 ; i<DST_BUFFER_SIZE_4KB ; i++) {
         for (j=0 ; j<1024 ; j++) {
            *buf++ = src_buf[j];
         }
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Copy int : time = %.03f s, speed = %.03f MB/s\n", ftime, ((float)DST_BUFFER_SIZE_BYTES / ftime) * nloops / (1024 * 1024));
   if (buffer_check_fill_value(dst_buf, DST_BUFFER_SIZE_WORDS, 0xbbbbbbbb) == FALSE) {
      printf("Dest buffer is not filled correctly\n");
   }

   /*
    * memcpy
    */
   buffer_fill(dst_buf, DST_BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      buf = dst_buf;
      for (i=0 ; i<DST_BUFFER_SIZE_4KB ; i++) {
         memcpy(buf, src_buf, 4096);
         buf += (4096 / sizeof(int));
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("mempcy : time = %.03f s, speed = %.03f MB/s\n", ftime, ((float)DST_BUFFER_SIZE_BYTES / ftime) * nloops / (1024 * 1024));
   if (buffer_check_fill_value(dst_buf, DST_BUFFER_SIZE_WORDS, 0xbbbbbbbb) == FALSE) {
      printf("Dest buffer is not filled correctly\n");
   }

#ifdef __amigaos4__
   /*
    * CopyMem
    */
   buffer_fill(dst_buf, DST_BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      buf = dst_buf;
      for (i=0 ; i<DST_BUFFER_SIZE_4KB ; i++) {
         CopyMem(src_buf, buf, 4096);
         buf += (4096 / sizeof(int));
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("CopyMem : time = %.03f s, speed = %.03f MB/s\n", ftime, ((float)DST_BUFFER_SIZE_BYTES / ftime) * nloops / (1024 * 1024));
   if (buffer_check_fill_value(dst_buf, DST_BUFFER_SIZE_WORDS, 0xbbbbbbbb) == FALSE) {
      printf("Dest buffer is not filled correctly\n");
   }

   /*
    * CopyMemQuick
    */
   buffer_fill(dst_buf, DST_BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      buf = dst_buf;
      for (i=0 ; i<DST_BUFFER_SIZE_4KB ; i++) {
         CopyMemQuick(src_buf, buf, 4096);
         buf += (4096 / sizeof(int));
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("CopyMemQuick : time = %.03f s, speed = %.03f MB/s\n", ftime, ((float)DST_BUFFER_SIZE_BYTES / ftime) * nloops / (1024 * 1024));
   if (buffer_check_fill_value(dst_buf, DST_BUFFER_SIZE_WORDS, 0xbbbbbbbb) == FALSE) {
      printf("Dest buffer is not filled correctly\n");
   }

   /*
    * CopyOnChipMem
    */
   if (ocm_buf) {
      buffer_fill(dst_buf, DST_BUFFER_SIZE_WORDS, 0xdeadcafe);
      gettimeofday(&tv_start, NULL);
      for (n=0 ; n<nloops ; n++) {
         buf = dst_buf;
         for (i=0 ; i<DST_BUFFER_SIZE_4KB ; i++) {
            CopyOnChipMem((ULONG *)ocm_buf, (ULONG *)buf, 4096);
            buf += (4096 / sizeof(int));
         }
      }
      gettimeofday(&tv_stop, NULL);
      timersub(&tv_stop, &tv_start, &tv_diff);
      ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
      printf("CopyOnChipMem : time = %.03f s, speed = %.03f MB/s\n", ftime, ((float)DST_BUFFER_SIZE_BYTES / ftime) * nloops / (1024 * 1024));
      if (buffer_check_fill_value(dst_buf, DST_BUFFER_SIZE_WORDS, 0xbbbbbbbb) == FALSE) {
         printf("Dest buffer is not filled correctly\n");
      }
   }
#endif

   /*
    * Free resources and exit
    */
#ifdef __amigaos4__
   if (src_buf) {
      FreeVec(src_buf);
   }
   if (dst_buf) {
      FreeVec(dst_buf);
   }

   if (ocm_buf) {
      ReleaseOnChipMem();
      DropInterface((struct Interface *)IOCM);
   }
#else
   if (src_buf_addr) {
      free(src_buf_addr);
   }
   if (dst_buf_addr) {
      free(dst_buf_addr);
   }
#endif

   return 0;
}

